package com.chucang.shucang.auth.config;

import com.chucang.shucang.auth.support.CustomOAuth2AccessTokenGenerator;
import com.chucang.shucang.auth.support.core.CustomOAuth2TokenCustomizer;
import com.chucang.shucang.auth.support.core.ShuCangDaoAuthenticationProvider;
import com.chucang.shucang.auth.support.core.FormIdentityLoginConfigurer;
import com.chucang.shucang.auth.support.handler.ShuCangAuthenticationFailureEventHandler;
import com.chucang.shucang.auth.support.handler.ShuCangAuthenticationSuccessEventHandler;
import com.chucang.shucang.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter;
import com.chucang.shucang.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider;
import com.chucang.shucang.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter;
import com.chucang.shucang.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider;
import com.chucang.shucang.common.base.constant.SecurityConstant;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.RequestMatcher;

import java.util.Arrays;

/**
 * @author flitsneak
 * @email flitsneak@gmail.com
 * @date 2022/9/15 16:53
 * @description 授权服务器配置
 */
@Configuration
@RequiredArgsConstructor
public class AuthorizationServerConfig {

    /**
     * token 存储 存在内存 数据 或者redis
     */
    private final OAuth2AuthorizationService authorizationService;

    /**
     * 授权服务器过滤器链
     *
     * @param http security
     * @return chain
     * @throws Exception e
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>();

        http.apply(authorizationServerConfigurer
                .tokenEndpoint(tokenEndpoint -> // 个性化认证授权端点
                        tokenEndpoint
                                .accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter
                                .accessTokenResponseHandler(new ShuCangAuthenticationSuccessEventHandler()) // 登录成功处理器
                                .errorResponseHandler(new ShuCangAuthenticationFailureEventHandler())// 登录失败处理器
                )
                .clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证
                        oAuth2ClientAuthenticationConfigurer
                                .errorResponseHandler(new ShuCangAuthenticationFailureEventHandler())
                )// 处理客户端认证异常
                .authorizationEndpoint(authorizationEndpoint ->
                        authorizationEndpoint// 授权码端点个性化confirm页面
                                .consentPage(SecurityConstant.CUSTOM_CONSENT_PAGE_URI))
        );

        RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

        DefaultSecurityFilterChain securityFilterChain =
                http
                        .requestMatcher(endpointsMatcher)
                        .authorizeRequests(authorizeRequests ->
                                authorizeRequests
                                        .anyRequest()
                                        .authenticated()
                        )
                        .apply(authorizationServerConfigurer
                                .authorizationService(authorizationService)// 扩展redis存储token
                                .providerSettings(
                                        ProviderSettings
                                                .builder()
                                                .issuer(SecurityConstant.PROJECT_LICENSE)
                                                .build()
                                )
                        )
                        .and()
                        .apply(new FormIdentityLoginConfigurer())
                        .and()
                        .build();
        // 注入自定义授权模式实现
        addCustomOAuth2GrantAuthenticationProvider(http);
        return securityFilterChain;
    }

    /**
     * 令牌生成规则实现 </br>
     * client:username:uuid
     *
     * @return OAuth2TokenGenerator
     */
    @Bean
    public OAuth2TokenGenerator<OAuth2Token> oAuth2TokenGenerator() {
        CustomOAuth2AccessTokenGenerator accessTokenGenerator = new CustomOAuth2AccessTokenGenerator();
        // 注入Token 增加关联用户信息
        accessTokenGenerator.setAccessTokenCustomizer(new CustomOAuth2TokenCustomizer());
        return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
    }

    /**
     * request -> xToken 注入请求转换器
     *
     * @return DelegatingAuthenticationConverter
     */
    private AuthenticationConverter accessTokenRequestConverter() {
        return new DelegatingAuthenticationConverter(Arrays
                .asList(
                        new OAuth2ResourceOwnerPasswordAuthenticationConverter(),
                        new OAuth2ResourceOwnerSmsAuthenticationConverter(),
                        new OAuth2RefreshTokenAuthenticationConverter(),
                        new OAuth2ClientCredentialsAuthenticationConverter(),
                        new OAuth2AuthorizationCodeAuthenticationConverter(),
                        new OAuth2AuthorizationCodeRequestAuthenticationConverter()
                )
        );
    }

    /**
     * 注入授权模式实现提供方
     * 1. 密码模式 </br>
     * 2. 短信登录 </br>
     */
    private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
        OAuth2AuthorizationService oAuth2AuthorizationService = http.getSharedObject(OAuth2AuthorizationService.class);

        OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
                authenticationManager, oAuth2AuthorizationService, oAuth2TokenGenerator()
        );

        OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(
                authenticationManager, oAuth2AuthorizationService, oAuth2TokenGenerator()
        );

        // 处理 UsernamePasswordAuthenticationToken
        http.authenticationProvider(new ShuCangDaoAuthenticationProvider());
        // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
        http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
        // 处理 OAuth2ResourceOwnerSmsAuthenticationToken
        http.authenticationProvider(resourceOwnerSmsAuthenticationProvider);
    }
}
